﻿using System;
using System.Diagnostics;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.IO;
using System.Runtime.InteropServices;
using par4log;

namespace sorting
{
    unsafe class Program
    {
        static StreamWriter sw;
        static Logger logger;

        public static void Main(string[] args)
        {
            try
            {
                var fileSize = new FileInfo(args[0]).Length;
                if (fileSize == 0)
                {
                    throw new System.NullReferenceException();
                }
                //logger = Logger.GetInstance("logs", true, 10000000, false);
                if (Directory.Exists("tmp"))
                    Directory.Delete("tmp", true);
                Directory.CreateDirectory("tmp");
                if (File.Exists(args[1]))
                    File.Delete(args[1]);

                var fsoutput = new FileStream(args[1], FileMode.OpenOrCreate);
                sw = new StreamWriter(fsoutput);

                var timer = Stopwatch.StartNew();
                var mmf = MemoryMappedFile.CreateFromFile(args[0], FileMode.Open, "myFile");
                int alreadySorted = 0;
                bool endOfFile = false, lastIteration = false;

                long offset = 0;
                const long capacity = 16777216 * 16; //16 * 16 Mb
                long viewSize = capacity ; 
                //200 * 100 * 1000;
                int arrSize = (int)viewSize / 2 + 1;
                while (!endOfFile)
                {
                    if (offset >= fileSize - viewSize)
                    {
                        viewSize = fileSize - offset;
                        lastIteration = true;
                    }
                    if (lastIteration)
                    {
                        endOfFile = true;
                    }
                    using (var accessor = mmf.CreateViewAccessor(offset, viewSize))
                    {
                        int index;//actually it's a length of the nums array
                        uint[] nums;
                        int unReadBytes = ReadData(accessor, offset, out nums, out index, arrSize);
                        offset -= unReadBytes;
                        offset += viewSize;

                        Array.Sort(nums, 0, index);
                        //WriteToTmpFileViaSW(Path.Combine("tmp", "tmp" + alreadySorted.ToString()), nums, index);
                        WriteToTmpFileViaMMF(Path.Combine("tmp", "tmp" + alreadySorted.ToString()), nums, index, capacity);

                        alreadySorted++;
                        //logger.Log(MSG_LEVEL.INFO, "Already sorted " + alreadySorted + " part with size " + index + " numbers");
                        Console.WriteLine("Already sorted {0} part with size {1} numbers", alreadySorted, index);
                    }
                }
                //MergeFilesViaSW(alreadySorted);
                MergeFilesViaMMF(alreadySorted);
                sw.Close();
                timer.Stop();
                //Directory.Delete("tmp", true);
                Console.WriteLine("Total Seconds {0}", timer.Elapsed.TotalSeconds);
                fsoutput.Close();
            }
            catch (OverflowException)
            {
                Console.WriteLine("too big number for int32");
                Console.ReadKey();
            }
            catch (FileNotFoundException)
            {
                Console.WriteLine("input file wasn't found");
                Console.ReadKey();
            }
            catch (FormatException)
            {
                Console.WriteLine("invalid data format");
                Console.ReadKey();
            }
            catch (OutOfMemoryException)
            {
                Console.WriteLine("too big input");
                Console.ReadKey();
            }
            catch (NullReferenceException)
            {
                Console.WriteLine("Input file is empty.");
                Console.ReadKey();
            }
            catch (UnauthorizedAccessException)
            {
                Console.WriteLine("invalid input format");
                Console.ReadKey();
            }
            catch (IOException)
            {
                Console.WriteLine("File is busy. Release files and restart");
                Console.ReadKey();
            }
            Console.ReadKey();
        }

        /*private static void WriteToTmpFileViaSW(string fileName, uint[] nums, int index)
        {
            var tmpSW = new StreamWriter(fileName, false);
            for (int k = 0; k < index; k++)
            {
                tmpSW.WriteLine(nums[k]);
            }
            tmpSW.Close();
        }
        private static void MergeFilesViaSW(int numFiles)
        {
            var srArray = new StreamReader[numFiles];
            var isEndedArray = new bool[numFiles];//shows if srArray[i] reached its end

            for (int i = 0; i < numFiles; i++)
            {
                srArray[i] = new StreamReader(Path.Combine("tmp", "tmp" + i.ToString()));
                isEndedArray[i] = false;
            }

            var a = new uint[numFiles];
            int index = 0;

            for (int i = 1; i < numFiles; i++)
                a[i] = uint.Parse(srArray[i].ReadLine());

            while (isEndedArray.Any(f => f == false))
            {
                if (!isEndedArray[index])
                    a[index] = uint.Parse(srArray[index].ReadLine());

                uint min = FindMinElement(a, out index);
                sw.Write(min + " ");

                if (srArray[index].EndOfStream)
                {
                    isEndedArray[index] = true;
                    a[index] = UInt32.MaxValue;
                }
            }
            foreach (StreamReader sr in srArray)
                sr.Close();
        }*/

        private static void WriteToTmpFileViaMMF(string fileName, uint[] nums, int index, long capacity)
        {
            if (File.Exists(fileName))
                File.Delete(fileName);

            using (var mmf_tmp = MemoryMappedFile.CreateFromFile(fileName, FileMode.Create, "tmp", capacity))
            {
                using (var mmf_tmp_acc = mmf_tmp.CreateViewAccessor(0, (index + 1)*4))
                {
                    long position = 0;
                    mmf_tmp_acc.Write(position, index);
                    position += 4;
                    for (int k = 0; k < index; k++)
                    {
                        mmf_tmp_acc.Write(position, nums[k]);
                        position += 4;
                    }
                }
            }
        }

        private static int ReadData(MemoryMappedViewAccessor accessor, long offset, out uint[] nums, out int index, int arrSize)
        {
            using (var safeViewHandle = accessor.SafeMemoryMappedViewHandle)
            {
                nums = new uint[arrSize];
                index = 0;
                byte* pStart = null;
                safeViewHandle.AcquirePointer(ref pStart);
                ulong correction = 0;
                // needed to correct offset because the view handle does not start at the offset specified in the CreateAccessor call
                // This makes AquirePointer nearly useless.
                // http://connect.microsoft.com/VisualStudio/feedback/details/537635/no-way-to-determine-internal-offset-used-by-memorymappedviewaccessor-makes-safememorymappedviewhandle-property-unusable
                pStart = Helper.Pointer(pStart, offset, out correction);
                var len = safeViewHandle.ByteLength - correction;
                bool digitFound = false;
                uint curInt = 0;
                byte current;
                for (ulong i = 0; i < len; i++)
                {
                    current = *(pStart + i);
                    if (current == (byte)' ' && digitFound)
                    {
                        nums[index] = curInt;
                        index++;
                        //  Console.WriteLine("Add {0}", curInt);
                        digitFound = false;
                        curInt = 0;
                    }
                    else
                    {
                        curInt = curInt * 10 + ((uint)current - '0');
                        digitFound = true;
                    }
                }

                // scan backwards to find partial read number
                int unread = 0;
                if (curInt != 0 && digitFound)
                {
                    byte* pEnd = pStart + len;
                    while (true)
                    {
                        pEnd--;
                        if (*pEnd == (byte)' ' || pEnd == pStart)
                        {
                            break;
                        }
                        unread++;

                    }
                }
                safeViewHandle.ReleasePointer();
                return unread;
            }
        }

        private static void MergeFilesViaMMF(int numFiles)
        {
            var isEndedArray = new bool[numFiles];//shows if srArray[i] reached its end
            var mmf_acc_array = new MemoryMappedViewAccessor[numFiles];
            var size = 1048576 * 64; //64 Mb
            var position = new long[numFiles];
            var amount = new long[numFiles];
            var mmf = new MemoryMappedFile[numFiles];
            var iteration = new int[numFiles];
            for (int i = 0; i < numFiles; i++)
            {
                mmf[i] = MemoryMappedFile.CreateFromFile(Path.Combine("tmp", "tmp" + i.ToString()));
                mmf_acc_array[i] = mmf[i].CreateViewAccessor(0, size);
                iteration[i]++;
                isEndedArray[i] = false;
            }
            var a = new uint[numFiles];
            int index = 0;

            for (int i = 0; i < numFiles; i++)
            {
                amount[i] = mmf_acc_array[i].ReadUInt32(position[i]);
                position[i] += 4;
            }

            while (isEndedArray.Any(f => f == false))
            {
                if (!isEndedArray[index])
                {
                    a[index] = mmf_acc_array[index].ReadUInt32(position[index]);
                    position[index] += 4;
                    if (position[index] == mmf_acc_array[index].Capacity)
                    {
                        mmf_acc_array[index] = mmf[index].CreateViewAccessor(size * iteration[index], size);
                        iteration[index]++;
                        position[index] = 0;
                    }
                    amount[index]--;
                }

                uint min = FindMinElement(a, out index);
                sw.Write(min + " ");
                if (amount[index] == 0)
                {
                    isEndedArray[index] = true;
                    a[index] = UInt32.MaxValue;
                }
            }
            for (int i = 0; i < numFiles; i++)
            {
                mmf_acc_array[i].Dispose();
                mmf[i].Dispose();
            }
        }

        private static uint FindMinElement(uint[] a, out int minIndex)
        {
            uint min = a[0];
            minIndex = 0;
            int length = a.Count();
            for (int i = 0; i < length; i++)
                if (a[i] < min)
                {
                    min = a[i];
                    minIndex = i;
                }
            return min;
        }
    }

    public unsafe static class Helper
    {
        static SYSTEM_INFO info;

        static Helper()
        {
            GetSystemInfo(ref info);
        }

        public static byte* Pointer(byte* pByte, long offset, out ulong diff)
        {
            var num = offset % info.dwAllocationGranularity;
            diff = (ulong)num; // return difference

            byte* tmp_ptr = pByte;

            tmp_ptr += num;

            return tmp_ptr;
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        internal static extern void GetSystemInfo(ref SYSTEM_INFO lpSystemInfo);

        internal struct SYSTEM_INFO
        {
            internal int dwOemId;
            internal int dwPageSize;
            internal IntPtr lpMinimumApplicationAddress;
            internal IntPtr lpMaximumApplicationAddress;
            internal IntPtr dwActiveProcessorMask;
            internal int dwNumberOfProcessors;
            internal int dwProcessorType;
            internal int dwAllocationGranularity;
            internal short wProcessorLevel;
            internal short wProcessorRevision;
        }
    }
}